/**
 * @author: 子龙
 */
package com.fzl.lotterysystem.service.mq;

import cn.hutool.core.date.DateUtil;
import com.fzl.lotterysystem.common.config.ExecutorConfig;
import com.fzl.lotterysystem.common.exception.ServiceException;
import com.fzl.lotterysystem.common.utils.JacksonUtil;
import com.fzl.lotterysystem.common.utils.MailUtil;
import com.fzl.lotterysystem.controller.param.DrawPrizeParam;
import com.fzl.lotterysystem.dao.dataobject.ActivityPrizeDO;
import com.fzl.lotterysystem.dao.dataobject.WinningRecordDO;
import com.fzl.lotterysystem.dao.mapper.ActivityPrizeMapper;
import com.fzl.lotterysystem.dao.mapper.WinningRecordMapper;
import com.fzl.lotterysystem.service.DTO.ConvertActivityStatusDTO;
import com.fzl.lotterysystem.service.DrawPrizeService;
import com.fzl.lotterysystem.service.activitystatus.ActivityStatusManager;
import com.fzl.lotterysystem.service.enums.ActivityPrizeStatusEnum;
import com.fzl.lotterysystem.service.enums.ActivityPrizeTiersEnum;
import com.fzl.lotterysystem.service.enums.ActivityStatusEnum;
import com.fzl.lotterysystem.service.enums.ActivityUserStatusEnum;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import static com.fzl.lotterysystem.common.config.DirectRabbitConfig.QUEUE_NAME;

@Slf4j
@Component
@RabbitListener(queues = QUEUE_NAME)
public class MqReceiver {

    @Autowired
    private DrawPrizeService drawPrizeService;

    @Autowired
    private ActivityStatusManager activityStatusManager;

    @Autowired
    @Qualifier("asyncServiceExecutor")
    private ThreadPoolTaskExecutor threadPoolTaskExecutor;

    @Autowired
    private MailUtil mailUtil;

    @Autowired
    private ActivityPrizeMapper activityPrizeMapper;

    @Autowired
    private WinningRecordMapper winningRecordMapper;

    @RabbitHandler
    public void process(Map<String, String> message) {
        //成功接收
        log.info("mq接受成功,消息message:{}", JacksonUtil.writeValueAsString(message));
        String paramString= message.get("messageData");
        DrawPrizeParam param=JacksonUtil.readValue(paramString, DrawPrizeParam.class);

        //处理抽奖的流程
        try {

            // 校验抽奖请求是否有效
            // 1、有可能前端发起两个一样的抽奖请求，对于param来说也是一样的两个请求
            // 2、param：最后一个奖项-》
            //      处理param1：活动完成、奖品完成
            //      处理param2: 回滚活动、奖品状态
            if (!drawPrizeService.checkDrawPrizeParam(param)) {
                return;
            }

            //状态扭转处理
            statusConvert(param);

            //保存中奖者名单
            List<WinningRecordDO> winningRecordDOList= drawPrizeService.saveWinnerRecords(param);

            //通知中奖者
            //抽奖之后的后续流程，异步（并发）
            syncExecute(winningRecordDOList);

            //若有异常，需要数据回滚，再次抛出异常
        }catch (ServiceException e){
            log.error("处理MQ错误{}:{}",e.getCode(),e.getMessage(),e);
            //回滚
            rollBack(param);
            throw e;
        }catch (Exception e){
            log.error("处理MQ错误",e);
        }


    }

    /**
     * 处理抽奖异常回滚
     * 恢复处理请求之前的状态，也就是扭转之前
     * 恢复
     * @param param
     */
    private void rollBack(DrawPrizeParam param) {
        //1.回滚状态：活动，活动奖品，活动人员
        //判断是否需要回滚
        if(!statusNeedRollback(param)){
            //状态不需要回滚：return
            return;
        }

        //需要回滚
        rollBackStatus(param);
        
        //2.回滚中奖者名单
        //判断是否需要回滚
        if(!WinnerNeedRollback(param)){
            //不需要回滚：return
            return;
        }
        
        //需要回滚 
        rollBackWinner(param);
    }


    /**
     * 是否回滚状态
     * @param param
     * @return
     */
    private boolean statusNeedRollback(DrawPrizeParam param) {
        //判断三张表是否已经扭转（正常）
        //在扭转状态时保证了事务的一致性，要么都扭转，要么都没（不包含活动表）
        //因此，只用判断人员或奖品是否扭转，就能判断出是否已经扭转
        //不能去判断活动是否已经扭转，因为活动必须奖品全部抽完才扭转
        //结论：判断奖品状态是否扭转
        ActivityPrizeDO activityPrizeDO=
                activityPrizeMapper.selectByAPId(param.getActivityId(),param.getPrizeId());

        if(activityPrizeDO.getStatus().equalsIgnoreCase(ActivityPrizeStatusEnum.COMPLETED.name())){
            //已经扭转完成，需要回滚
            return true;
        }
        return false;
    }

    /**
     * 恢复相关状态
     * @param param
     */
    private void rollBackStatus(DrawPrizeParam param) {
        //恢复状态：ActivityStatusManager
        ConvertActivityStatusDTO convertActivityStatusDTO=new ConvertActivityStatusDTO();
        convertActivityStatusDTO.setActivityId(param.getActivityId());
        convertActivityStatusDTO.setTargetActivityStatus(ActivityStatusEnum.RUNNING);
        convertActivityStatusDTO.setPrizeId(param.getPrizeId());
        convertActivityStatusDTO.setTargetPrizeStatus(ActivityPrizeStatusEnum.INIT);
        convertActivityStatusDTO.setUserIds(param.getWinnerList()
                .stream()
                .map(DrawPrizeParam.Winner::getUserId).collect(Collectors.toList())
        );
        convertActivityStatusDTO.setTargetUserStatus(ActivityUserStatusEnum.INIT);

        activityStatusManager.rollBackHandlerEvent(convertActivityStatusDTO);
    }


    /**
     * 是否回滚中奖人
     * @param param
     * @return
     */
    private boolean WinnerNeedRollback(DrawPrizeParam param) {
        //判断活动中的奖品是否有中奖者
        int count=winningRecordMapper.countByAPId(param.getActivityId(),param.getPrizeId());
        if(count>0){
            return true;
        }
        return false;
    }


    /**
     * 恢复中奖人
     * 删除中奖者表的信息
     * @param param
     */
    private void rollBackWinner(DrawPrizeParam param) {
        drawPrizeService.deleteRecords(param.getActivityId(),param.getPrizeId());
    }




    /**
     * 并发处理后续流程
     * @param winningRecordDOList
     */
    private void syncExecute(List<WinningRecordDO> winningRecordDOList) {
        //线程池

        // 邮件通知
        threadPoolTaskExecutor.execute(()->sendMail(winningRecordDOList));

    }

    /**
     * 发邮件
     *
     * @param winningRecordDOList
     */
    private void sendMail(List<WinningRecordDO> winningRecordDOList) {
        if (CollectionUtils.isEmpty(winningRecordDOList)) {
            log.info("中奖列表为空，不用发邮件！");
            return;
        }
        for (WinningRecordDO winningRecordDO : winningRecordDOList) {
            // Hi,胡一博。恭喜你在抽奖活动活动中获得二等奖:吹风机。获奖奖时间为18:18:44,请尽快领取您的奖励
            String context = "Hi，" + winningRecordDO.getWinnerName() + "。恭喜你在"
                    + winningRecordDO.getActivityName() + "活动中获得"
                    + ActivityPrizeTiersEnum.forName(winningRecordDO.getPrizeTier()).getMessage()
                    + "：" + winningRecordDO.getPrizeName() + "。获奖时间为"
                    + DateUtil.formatTime(winningRecordDO.getWinningTime()) + "，请尽快领 取您的奖励！";
            mailUtil.sendSampleMail(winningRecordDO.getWinnerEmail(),
                    "中奖通知", context);
        }
    }


    /**
     * 扭转状态代码
     * @param param
     */
    private void statusConvert(DrawPrizeParam param) {
        ConvertActivityStatusDTO convertActivityStatusDTO=new ConvertActivityStatusDTO();
        convertActivityStatusDTO.setActivityId(param.getActivityId());
        convertActivityStatusDTO.setTargetActivityStatus(ActivityStatusEnum.COMPLETED);
        convertActivityStatusDTO.setPrizeId(param.getPrizeId());
        convertActivityStatusDTO.setTargetPrizeStatus(ActivityPrizeStatusEnum.COMPLETED);
        convertActivityStatusDTO.setUserIds(param.getWinnerList()
                .stream()
                .map(DrawPrizeParam.Winner::getUserId)
                .collect(Collectors.toList())
        );
        convertActivityStatusDTO.setTargetUserStatus(ActivityUserStatusEnum.COMPLETED);

        activityStatusManager.handlerEvent(convertActivityStatusDTO);
    }




    //这样写高耦合，不利于维护
//    private void statusConvert(DrawPrizeParam param) {
//        //活动：RUNING--》COMPL，全部奖品抽完才可以该状态
//        //奖品：INIT---》COMPLI
//        //人员列表：INIT---》COMPL
//
//        //先扭转奖品状态
//        //查询活动关联的奖品信息
//        //判断当前状态是否是COMPL，如果是则不用，否则扭转
//
//
//        //再扭转人员
//        //查询活动关联的人员信息
//        //判断当前状态是否是COMPL，如果是则不用，否则扭转
//
//        //最后扭转活动
//        //查询活动关联的活动信息
//        //判断当前状态是否是COMPL，如果是则不用，否则扭转，必须是奖品全部被抽完
//
//
//        //更新完整活动的缓存
//    }
}